﻿using System;
using System.Collections.Generic;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Commands.BuiltInCommands;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Commands
{
    public class RemoveModuleCommand : BaseCommand
    {
        private NodisizerAreaHandler mNodisizerArea;
        private IModuleContainer mParentModule;
        private BaseModule mModule;
        private BaseModulizerUI mNodisizer;
        private Dictionary<ModuleInput, ModuleOutput> mCurrentInputConnections = new Dictionary<ModuleInput, ModuleOutput>();
        private Dictionary<ModuleOutput, List<ModuleInput>> mCurrentOutputConnections = new Dictionary<ModuleOutput, List<ModuleInput>>();
        private ModulizerUIv2 mNodisizerV2;

        public override object[] ReferenceObjects
        {
            get { return RefereceObject(mModule); }
        }

        public ModulePanel ModulePanel { get; private set; }

        public RemoveModuleCommand(NodisizerAreaHandler nodisizerArea, BaseModulizerUI nodisizer, BaseModule module)
        {
            if (nodisizerArea == null)
            {
                throw new System.ArgumentNullException(nameof(nodisizerArea));
            }
            if (nodisizer == null)
            {
                throw new System.ArgumentNullException(nameof(nodisizer));
            }
            if (module == null)
            {
                throw new System.ArgumentNullException(nameof(module));
            }

            mNodisizerArea = nodisizerArea;

            mNodisizer = nodisizer;

            mModule = module;

            mParentModule = module.ParentModule as IModuleContainer;
        }

        public RemoveModuleCommand(ModulizerUIv2 mNodisizer1, BaseModule module)
        {
            mNodisizerV2 = mNodisizer1;
            mModule = module;
            mParentModule = module.ParentModule as IModuleContainer;
        }

        protected override void Execute()
        {
            mCurrentInputConnections.Clear();
            mCurrentOutputConnections.Clear();

            BaseModule parent = mModule.ParentModule;

            // Disconnect all inputs.
            DisconnectInputs(mModule);

            // Disconnect all outputs.
            DisconnectOutputs(mModule);

            // Disconnect all parameters.
            // ...

            // Remove module from engine.
            mParentModule.RemoveSubModule(mModule);
            RemoveSubModulesFromEngine(mModule);

            // Remove module panel.
            if (mNodisizerV2 != null)
            {
                mNodisizerV2.TrackArea.Editor.SelectorHandler.ClearSelection();
                mNodisizerV2.UpdateModuleUIUp(parent);

                bool animateToFill = mModule.ParentModule != mNodisizerV2.CurrentModuleContainer;
                mNodisizerV2.GoInto(parent, animateToFill);

                mNodisizerV2.UpdateParameterStates();
            }
            else
            {
                mNodisizer.RebuildSynthGraph();
                mNodisizerArea.Editor.SelectorHandler.ClearSelection();
                mNodisizer.UpdateParameterStates();
            }
        }

        private void RemoveSubModulesFromEngine(BaseModule module)
        {
            if (module is IModuleContainer)
            {
                IModuleContainer group = module as IModuleContainer;

                foreach (var subModule in group.SubModules)
                {
                    if (mNodisizerV2 != null)
                    {
                        mNodisizerV2.mModuleManager.RemoveModule(subModule);
                    }
                    else
                    {
                        mNodisizer.Modulizer.mModuleManager.RemoveModule(subModule);
                    }

                    RemoveSubModulesFromEngine(subModule);
                }
            }
        }

        private void AddSubModulesToEngine(BaseModule module)
        {
            if (module is IModuleContainer)
            {
                IModuleContainer group = module as IModuleContainer;

                foreach (var subModule in group.SubModules)
                {
                    if (mNodisizerV2 != null)
                    {
                        mNodisizerV2.mModuleManager.AddModule(subModule);
                    }
                    else
                    {
                        mNodisizer.Modulizer.mModuleManager.AddModule(subModule);
                    }

                    AddSubModulesToEngine(subModule);
                }
            }
        }

        private void DisconnectInputs(BaseModule fromModule)
        {
            foreach (var input in fromModule.Inputs)
            {
                if (input.IsConnected)
                {
                    mCurrentInputConnections.Add(input, input.FromOutput);
                    input.FromOutput = null;
                }
            }

            if (fromModule is IModuleContainer)
            {
                foreach (var subModule in (fromModule as IModuleContainer).SubModules)
                {
                    DisconnectInputs(subModule);
                }
            }
        }

        private void DisconnectOutputs(BaseModule fromModule)
        {
            foreach (var output in fromModule.Outputs)
            {
                if (output.IsConnected)
                {
                    mCurrentOutputConnections.Add(output, new List<ModuleInput>());
                    for (int i = output.ConnectedInputs.Count - 1; i >= 0; i--)
                    {
                        ModuleInput input = output.ConnectedInputs[i];
                        mCurrentOutputConnections[output].Add(input);
                        output.RemoveInput(input);
                    }
                }
            }

            if (fromModule is IModuleContainer)
            {
                foreach (var subModule in (fromModule as IModuleContainer).SubModules)
                {
                    DisconnectOutputs(subModule);
                }
            }
        }
        
        protected override void UnExecute()
        {
            // Add module to engine.
            AddSubModulesToEngine(mModule);
            mParentModule.AddSubModule(mModule);

            // Insert module panel.
            if (mNodisizerV2 != null)
            {

            }
            else
            {
                ModulePanel = mNodisizer.CreateModulePanel(mModule);
            }

            // Connect all inputs.
            ConnectInputs();

            // Connect all outputs.
            ConnectOutputs();

            // Connect all parameters.
            // ...

            mCurrentInputConnections.Clear();
            mCurrentOutputConnections.Clear();

            if (mNodisizerV2 != null)
            {
                mNodisizerV2.GoInto(mModule.ParentModule);
                mNodisizerV2.UpdateModuleUIUp(mModule.ParentModule);
                mNodisizerV2.UpdateParameterStates();
            }
            else
            {
                mNodisizer.RebuildSynthGraphModuleParent(mModule);
                mNodisizer.UpdateParameterStates();
            }
        }

        private void ConnectInputs()
        {
            foreach (var input in mCurrentInputConnections)
            {
                ModuleInput moduleInput = input.Key;
                ModuleOutput moduleOutput = input.Value;
                moduleInput.FromOutput = moduleOutput;
            }
        }

        private void ConnectOutputs()
        {
            foreach (var output in mCurrentOutputConnections)
            {
                ModuleOutput moduleOutput = output.Key;
                List<ModuleInput> moduleInputs = output.Value;

                for (int i = moduleInputs.Count - 1; i >= 0; i--)
                {
                    moduleOutput.AddInput(moduleInputs[i]);
                }
            }
        }

        public override void Dispose()
        {
            // Disconnect all inputs.
            // ...

            // Disconnect all outputs.
            // ...

            // Disconnect all parameters.
            // ...

            // Delete module.
            // ...

            base.Dispose();
        }

        public override string ToString()
        {
            return "Removed module.";
        }
    }
}
